Chapter 24
CSocket Programming

by Davis Chapman

In This Chapter

  How Do Network Communications Work? 852
  Winsock and MFC 855
  Building a Networked Application 869

Thanks in part to the explosion in popularity of the Internet, more applications have the capability of communicating with other applications over networks, including the Internet. With Microsoft building networking capabilities into its operating systems, starting with Windows NT and Windows 95, these capabilities are becoming commonplace in all sorts of applications.

Some applications perform simple networking tasks, such as checking with a Web site to see whether there are any updates to the program and giving the user the option of updating his or her copy of the program. Some word processing applications format documents as Web pages, giving the user the option of loading the pages onto the Web server. Computer games enable the user to play against another person halfway around the world, instead of just competing against the game itself.

Applications can have any number of networking functions, and they all are built around the Winsock interface. If you know and understand how to program using the Winsock interface, and the MFC Winsock classes, this entire realm of application programming is open to you, expanding your programming options considerably.

How Do Network Communications Work?

Most applications that communicate over a network, whether it’s the Internet or a small office network, use the same principles and functionality to perform their communication. One application sits on a computer, waiting for another application to open a communication connection. This application is “listening” for this connection request, much as you listen for the phone to ring if you are expecting someone to call.

Meanwhile, another application, most likely on another computer (but not necessarily), tries to connect to the first application. This attempt to open a connection is similar to calling someone on the telephone. You dial the number and hope that the other person is listening for the phone on the other end. As the person making the call, you have to know the phone number of the person you are calling. If you don’t know the phone number, you can look it up using the person’s name. Likewise, the application trying to connect to the first application has to know the network location, or address, of the first application.

When the connection is made between the two applications, messages can pass back and forth between the two applications, much as you can talk to the person on the other end of the phone. This connection is a two-way communications channel, with both sides sending information, as seen in Figure 24.l.


Figure 24.1  The basic socket connection process.

Finally, when one or both sides have finished their sides of the conversation, the connection is closed, much as you hang up the phone when you have finished talking to the person you called. When the connection is closed from either side, the other side can detect it and close its side, just as you can tell if the person on the other end of the phone call has hung up on you or if you’ve been disconnected by some other means. This is a basic explanation of how network communications work between two or more applications.


Note:  

This is a basic description of how network communications work with the TCP/IP network protocol, which is the primary network protocol over the Internet. Many other network protocols use a subtle variation on this description. Other protocols, such as the UDP protocol, are more like radio broadcasts, where there is no connection between the two applications; one sends messages, and the other is responsible for making sure that it receives all of the messages.


Sockets, Ports, and Addresses

The basic object used by applications to perform most network communications is called a socket. Sockets were first developed on UNIX at the University of California at Berkeley. Sockets were designed so that most network communications between applications could be performed in the same way that these same applications would read and write files. Sockets have progressed quite a bit since then, but the basics of how they work are still the same.

During the days of Windows 3.x, before networking was built into the Windows operating system, you could buy the network protocols required for network communications from numerous different companies. Each of these companies had a slightly different way that an application performed network communications. As a result, any applications that did perform network communications had a list of the different networking software that the application would work with. Many application developers were not happy with this situation. As a result, all the networking companies, including Microsoft, got together and developed the Winsock (Windows Sockets) API. This provided all application developers with a consistent API to perform all network communications, regardless of the networking software used.

When you want to read or write a file, you must use a file object to point to the file. A socket is similar; it is an object used to read and write messages that travel between applications.

Making a socket connection to another application does require a different set of information than opening a file. To open a file, you need to know the file’s name and location. To open a socket connection, you need to know the computer on which the other application is running and the port on which it’s listening. A port is like a phone extension, and the computer address is like the phone number. If you call someone at a large office building, you can dial the main office number, but then you need to specify the extension number, as shown in Figure 24.2. As with the phone number, you can look up the port number if you don’t already know what it is, but this requires your computer to be configured with the information about which port the connecting application is listening on. If you specify the wrong computer address, or port number, you might get a connection to a different application; as with making the phone call, someone other than the person you called might answer the phone call. You also might not get an answer at all if there is no application listening at the other end.


Figure 24.2  Ports are used to route network communications to the correct application.


Note:  

Only one application can be listening on any specific port on a single computer. Although numerous applications can listen for connection requests on a single computer at the same time, each of these applications must listen on a different port.


Winsock and MFC

When you build applications with MFC, you can use the MFC Winsock classes to add network communications capabilities with relative ease. The base class, CAsyncSocket, provides complete, event-driven socket communications. You can create your own descendant socket class that captures and responds to each of these events. The CSocket class is a descendant of the CAsyncSocket class, and encapsulates and simplifies some of the functionality of the base class.



Initializing the Winsock Environment

Before you can use any of the Winsock MFC classes, you have to initialize the Winsock environment for your application. This is done with a single function call in the application instance initialization, AfxSocketInit. This function can take a single WSADATA structure as an optional parameter. If you supply this structure to this function, it will be populated with information about the version of Winsock that is currently in use on the computer on which your application is running. Unless you really need to know some of the information that is returned in this structure, you don’t need to pass it as a parameter, as in the following:

BOOL CSockApp::InitInstance()
{
    if (!AfxSocketInit())
    {
        AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
        return FALSE;
    }
.
.
.
}

If you include this function in the instance initialization function, the Winsock environment will be correctly initialized and shut down by your application.


Tip:  

If you use the Visual C++ Wizards to create your project shell, and specify to include support for Winsock in your application shell, the AfxSocketInit function is automatically added to your application shell.


Creating a Socket

To create a socket that you can use in your application, the first thing you need to do is declare a variable of CAsyncSocket, CSocket, or your descendant class, as a class member for one of the main application classes:

class CMyDlg : public CDialog
{
.
.
.
private:
    CAsyncSocket m_sMySocket;
};

Before you can begin using the socket object, you must call its Create method. This actually creates the socket and prepares it for use. How you call the Create method depends on how you will be using the socket. If you will be using the socket to connect to another application, as the one placing the call (the client), you do not need to pass any parameters to the Create method:

if (m_sMySocket.Create())
{
    // Continue on
}
else
    // Perform error handling here

However, if the socket is going to be listening for another application to connect to it, waiting for the call (the server), you need to pass at least the port number on which the socket should be listening:

if (m_sMySocket.Create(4000))
{
    // Continue on
}
else
    // Perform error handling here

You can include other parameters in the Create method call, such as the type of socket to create, the events that the socket should respond to (CAsyncSocket only), and the address that the socket should listen on (in case the computer has more than one network card).


Note:  

There are two types of sockets that you can create using the MFC Winsock classes. These are streaming, or TCP, sockets, and datagram, or UDP, sockets. The streaming sockets are connection-based, and have guaranteed delivery functionality built in. The datagram sockets are connectionless, and require you to write the code to make sure that the packets are received and, in the receiving application, placed in the order that they were sent. If a particular packet is not received, you also have to write the code to have the receiving application request that a particular packet be resent, and on the sending application, to resend the missing packet of data. To specify that a socket is to be a streaming socket, pass SOCK_STREAM as the second argument to the Create method. To specify that a socket should be a datagram socket, pass SOCK_DGRAM as the second argument.



Note:  

If you are building a server application that might be running on a computer with more than one network card installed, you might need to specify the network address that the socket will be listening on. This will tell the socket that it is only listening for incoming connection requests through a specific network card. To do this, you pass the network address to bind the socket to as the last argument to the Create method. This will be the fourth argument for the CAsyncSocket class, and the third argument for the CSocket class. The network address should be passed as a string, in the standard TCP/IP form 127.0.0.1.


Making a Connection

After you create a socket, you are ready to open a connection with it. Three steps go along with opening a single connection. Two of these steps take place on the server—the application listening for the connection—and the third step takes place on the client—the one making the call.

For the client, opening the connection is a simple matter of calling the Connect method. The client has to pass two parameters to the Connect method: the computer name, or network address, and the port of the application to connect to. The Connect method could be used in the following two ways:

if (m_sMySocket.Connect(“thatcomputer.com”, 4000))
{
    // Continue on
}
else
    // Perform error handling here

The second form is

if (m_sMySocket.Connect(“127.0.0.1”, 4000))
{
    // Continue on
}
else
    // Perform error handling here

After the connection is made, if you are using the CAsyncSocket class, or your own class that you derived from the CAsyncSocket class, an event is triggered to let your application know that it is connected or that there were problems and the connection couldn’t be made. (How these events work is covered in “Socket Events,” later in this chapter.) If you are using the CSocket class, the Connect function will not return until the connection has been made, or an error occurred that prevented the connection from being made.

For the server, or listening, side of the connection, the application first must tell the socket to listen for incoming connections by calling the Listen method. The Listen method takes only a single argument, which you do not need to supply. This parameter specifies the number of pending connections that can be queued, waiting for the connection to be completed. By default, this value is 5, which is the maximum. The Listen method can be called as follows:

if (m_sMySocket.Listen())
{
    // Continue on
}
else
    // Perform error handling here

Whenever another application is trying to connect to the listening application, an event is triggered in the CAsyncSocket class (and custom descendants) to let the application know that the connection request is there. The listening application must accept the connection request by calling the Accept method. This method requires the use of a second CAsyncSocket variable, which is connected to the other application. When a socket is placed into listen mode, it stays in listen mode. Whenever connection requests are received, the listening socket creates another socket, which is connected to the other application. This second socket should not have the Create method called for it because the Accept method creates the socket. You call the Accept method as follows:

if (m_sMySocket.Accept(m_sMySecondSocket))
{
    // Continue on
}
else
    // Perform error handling here

At this point, the connecting application is connected to the second socket on the listening application.

With the CSocket class, incoming connections are detected and accepted by calling the Accept function, as shown previously. When using the CSocket class, the Accept function will not return until a connection request has been received and accepted.

Sending and Receiving Messages

Sending and receiving messages through a socket connection gets slightly involved. Because you can use sockets to send any kind of data, and the sockets don’t care what the data is, the functions to send and receive data expect to be passed a pointer to a generic buffer. For sending data, this buffer should contain the data to be sent. For receiving data, this buffer will have the received data copied into it. As long as you are sending and receiving strings and text, you can use fairly simple conversions to and from CString variables with these buffers.



To send a message through a socket connection, you use the Send method. This method requires two parameters and has a third, optional parameter that can be used to control how the message is sent. The first parameter is a pointer to the buffer that contains the data to be sent. If your message is in a CString variable, you can use the LPCTSTR operator to pass the CString variable as the buffer. The second parameter is the length of the buffer. The method returns the amount of data that was sent to the other application. If an error occurs, the Send function returns SOCKET_ERROR. You can use the Send method as follows:

CString strMyMessage;
int iLen;
int iAmtSent;
.
.
.
iLen = strMyMessage.GetLength();
iAmtSent = m_sMySocket.Send(LPCTSTR(strMyMessage), iLen);
if (iAmtSent == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    // Everything’s fine
}

When data is available to be received from the other application, an event is triggered on the receiving application for the CAsyncSocket and descendant classes. This lets your application know that it can receive and process the message. To get the message, the Receive method must be called. This method takes the same parameters as the Send method with a slight difference. The first parameter is a pointer to a buffer into which the message can be copied. The second parameter is the size of the buffer. This tells the socket how much data to copy (in case more is received than will fit into the buffer). Like the Send method, the Receive method will return the amount that was copied into the buffer. If an error occurs, the Receive method also returns SOCKET_ERROR. If the message your application is receiving is a text message, it can be copied directly into a CString variable. This allows you to use the Receive method as follows:

char *pBuf = new char[1025];
int iBufSize = 1024;
int iRcvd;
CString strRecvd;

iRcvd = m_sMySocket.Receive(pBuf, iBufSize);
if (iRcvd == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    pBuf[iRcvd] = NULL;
    strRecvd = pBuf;
    // Continue processing the message
}


Tip:  

When you’re receiving text messages, it’s always a good idea to place a NULL in the buffer position just after the last character received, as in the preceding example. There might be garbage characters in the buffer that your application might interpret as part of the message if you don’t add the NULL to truncate the string.


As with most CSocket versions of these functions, the Receive function will not return until data has been received from the connected application.

If you are using datagram sockets, there are alternative versions of these two methods that you will want to use. These methods are the SendTo and ReceiveFrom methods. These functions work the same as their streaming counterparts, only with the addition of the network address and port to send the data to (with the SendTo method), or variables to store the address of the application you are receiving from (for the ReceiveFrom method).

Closing the Connection

When your application has finished all of its communications with the other application, it can close the connection by calling the Close method. The Close method doesn’t take any parameters, and you use it as follows:

m_sMySocket.Close();


Note:  

The Close function is one of the few CAsyncSocket and CSocket methods that does not return a status code. For all the previous member functions that this chapter has examined, you can capture the return value to determine if an error has occurred.


Sometimes you might want to shut down a socket before closing it. You can shut down a socket by using the ShutDown method. This method takes a single integer parameter, which specifies whether to shut down the sending or receiving of data over the socket. By default, the ShutDown method disables sending of data over a socket. You can specify which socket is disabled by passing the values shown in Table 24.1.

Table 24.1 Socket ShutDown Parameter Values

Value Description

0 Prevents receiving of incoming data packets over the socket
1 Prevents the sending of data packets through the socket
2 Prevents both the sending and receiving of data packets through the socketm


Note:  

Calling the ShutDown method on a socket does not close the connection or release any of the resources being used by the socket. You will still need to close the socket using the Close method.


Socket Events

The primary reason that you create your own descendant class of CAsyncSocket or CSocket is that you want to capture the events that are triggered when messages are received, connections are completed, and so on. The CAsyncSocket class has a series of functions that are called for each of these various events. These functions all use the same definition—the function name is the only difference—and they are intended to be overridden in descendant classes. All of these functions are declared as protected members of the CAsyncSocket class and probably should be declared as protected in your descendant classes. The functions all have a single integer parameter, which is an error code that should be checked to make sure that no error has occurred. Table 24.2 lists these event functions and the events they signal.

Table 24.2 CAsyncSocket Overridable Event-Notification Functions

Function Event Description

OnAccept This function is called on a listening socket to signal that a connection request from another application is waiting to be accepted.
OnClose This function is called on a socket to signal that the application on the other end of the connection has closed its socket or that the connection has been lost. This should be followed by closing the socket that received this notification.
OnConnect This function is called on a socket to signal that the connection with another application has been completed and that the application can now send and receive messages through the socket.
OnOutOfBandData This function is called when out-of-band data has been received. Out-of-band data is sent over a logically independent channel, and is used to send urgent data that is not part of the regular communications between the two connected applications. The Send and Receive methods both have a third parameter, which can be passed a flag, MSG_OOB, to send and receive out-of-band data.
OnReceive This function is called to signal that data has been received through the socket connection and that the data is ready to be retrieved by calling the Receive function.
OnSend This function is called to signal that the socket is ready and available for sending data. This function is called right after the connection has been completed. Usually, the other time that this function is called is when your application has passed the Send function more data than can be sent in a single packet. In this case, this is a signal that all of the data has been sent, and the application can send the next bufferful of data.



In addition to these overridable event functions, the CSocket class provides one additional overridable function, OnMessagePending. This function is called when there are messages pending in the application event message queue. This enables you to look for particular Windows messages and respond to them in your CSocket class.

Controlling Event Triggering

By default, the CAsyncSocket class calls all of the overridable functions in Table 24.2, whereas the CSocket class doesn’t call any of them. So what if you want to have your descendant class be somewhere in between, calling some of these functions, while ignoring the others? Well, you’re in luck. There are two ways of controlling which of these event functions are triggered.

The first way to specify which of these event functions are called is available only with the CAsyncSocket class, and any custom classes directly descended from it. In the Create method, the third parameter that you can supply is a flag value that specifies which of these events to trigger. The CSocket class overrides this method, preventing you from providing this flag value. By default, the CAsyncSocket Create method combines all of the event flag values, specifying that all of the event functions be triggered.

The second method of specifying which events are triggered is available to the descendant classes of both CAsyncSocket and CSocket. This is the AsyncSelect method. This method takes only the combination flag to define which events to trigger. You can call the AsyncSelect method as follows:

iErr = m_sMySocket.AsyncSelect(FD_READ | FD_CONNECT | FD_CLOSE);
if (iErr == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    // Continue processing
}

The default value for the parameter for the AsyncSelect method is to specify that all of the event functions be triggered. As a result, if you wanted to turn all event triggering off and then back on, you could first turn all events off by passing a zero as the flag value, as follows:

iErr = m_sMySocket.AsyncSelect(0);

And then to turn all event triggering back on, don’t supply a value for the flag as follows:

iErr = m_sMySocket.AsyncSelect();

The flag values that you can supply for the AsyncSelect and Create (CAsyncSocket only) functions are listed in Table 24.3.

Table 24.3 Socket Event-Notification Flags

Flag Description

FD_READ Triggers and calls the OnReceive function when data has arrived for reading.
FD_WRITE Triggers and calls the OnSend function when the outbound Winsock buffers are available for sending data. This event function tells your application when it can send data.
FD_OOB Triggers and calls the OnOutOfBandData function when out-of-band data has been received and needs to be read.
FD_ACCEPT Triggers and calls the OnAccept function to inform your application that there is an inbound connection request on your listening socket. You should follow this with the Accept method to complete the connection.
FD_CONNECT Triggers and calls the OnConnect function to inform your application that the connection request your application initiated with the Connect method has been completed. This event will be immediately followed by the OnSend event function to inform your application that it can now send data to the connected application.
FD_CLOSE Triggers and calls the OnClose function to inform your application that the socket connection has been closed by the connected application.

Detecting Errors

Whenever any of the CAsyncSocket or CSocket member functions return an error, either FALSE for most functions or SOCKET_ERROR on the Send and Receive functions, you can call the GetLastError method to get the error code. This function returns only error codes, and you have to look up the translation yourself. All the Winsock error codes are defined with constants, so you can use the constants in your code to determine the error message to display for the user, if any. You can use the GetLastError function as follows:

int iErrCode;

iErrCode = m_sMySocket.GetLastError();
switch (iErrCode)
{
case WSANOTINITIALISED:
.
.
.
}

Getting Socket Information

At times you need to get information about the state of the sockets in your application, such as the address and port of the application on the other end of the connection, and whether the socket is waiting on a blocking function to complete. There are also several options that you can set or check on the sockets in your applications.

Getting the Connected Address

When you have a socket that is connected to another application, you can find out the network address of the other application. You can do this by calling the GetPeerName method, passing it a pointer to a CString and an unsigned integer. The address and port of the other application are returned in these two variables. You can call the GetPeerName method as follows:

CString sPeerAddress;
UINT iPeerPort;

iErr = m_sMySocket.GetPeerName(&PeerAddress, &iPeerPort);
if (iErr == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    cout << “Peer Network Address: ” << sPeerAddress << “\n”;
    cout << “Peer Port: ” << iPeerPort << “\n”;
    // Continue processing
}

Likewise, if you did not bind your socket to a specific port or network address, which you usually do not unless your socket is listening for incoming connections, you can get the same information about your application’s socket by calling the GetSockName method, as follows:

CString sMyAddress;
UINT iMyPort;

iErr = m_sMySocket.GetSockName(&MyAddress, &iMyPort);
if (iErr == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    cout << “My Network Address: ” << sMyAddress << “\n”;
    cout << “My Port: ” << iMyPort << “\n”;
    // Continue processing
}


Note:  

With the GetSockName method, you are likely to get a network address of 0.0.0.0 as your application’s network address. This is because your socket was never bound to a specific network address, and thus is using the default address (0.0.0.0) for its outbound connection requests. The Winsock interface translates this into the network address of your computer, so that even though your application sees your network address as all zeros, the application that you are connected to does see the actual computer network address.




Getting and Setting Options

Several options can be set on a socket that affect how the socket behaves. You can build most of your applications without needing to adjust any of these options. For those situations where you do need to adjust or check some of these settings, you can use the GetSockOpt and SetSockOpt methods.

The GetSockOpt method is used to check the current setting of various socket options. This method takes four parameters, of which the first three are required. The first parameter specifies which option you want the value of. The second is a pointer to a buffer into which the current value of the option is to be copied. The third parameter is an integer pointer to a variable containing the size of the buffer into which the setting value is to be copied. The fourth parameter specifies which level the option is defined for, the socket or protocol level. The default is the socket level, SOL_SOCKET, but there is one option that is defined at the protocol level, IPPROTO_TCP. The available socket options and their data types are listed in Table 24.4.

Table 24.4 Socket Options

Option Data Type Description

SO_ACCEPTCONN BOOL The socket is listening for an inbound connection request.
SO_BROADCAST BOOL The socket is configured for the transmission of broadcast messages.
SO_DEBUG BOOL Debugging is enabled on the socket.
SO_DONTLINGER BOOL If this option is set to TRUE, the SO_LINGER option is disabled.
SO_DONTROUTE BOOL Routing is disabled.
SO_ERROR int Retrieves the error status and clears the status.
SO_KEEPALIVE BOOL Keep-alives are being sent.
SO_LINGER struct LINGER Returns the current linger options.
SO_OOBINLINE BOOL Out-of-band data is being received in the normal data stream.
SO_RCVBUF int The buffer size used for receiving data.
SO_REUSEADDR BOOL The socket can be bound to an address (and port) that is already being used.
SO_SNDBUF int The buffer size used for sending data.
SO_TYPE int The type of socket (SOCK_STREAM or SOCK_DGRAM).
TCP_NODELAY BOOL Disables the Nagle algorithm for send coalescing.

To set or change any of these options, the SetSockOpt method takes the same four parameters with one small exception: The third parameter, the size of the buffer containing the value to set the option to, is passed as an integer, and not as a pointer to an integer. The other thing to keep in mind with the SetSockOpt method is that you can use it to set or change the value of any of the options in Table 24.4 except for the SO_ACCEPTCONN, SO_ERROR, and SO_TYPE options, which are read-only.

To check and set the value of a particular option, you can use these two methods as follows:

BOOL bStatus;
int iStatusSize;

iStatusSize = sizeof(BOOL);
iErr = m_sMySocket.GetSockOpt(SO_KEEPALIVE, &bStatus, &iStatusSize);
if (iErr == SOCKET_ERROR)
{
    // Do some error handling here
}
else
{
    // Are we sending keep-alives?
    if (!bStatus)
    {
        // if not, then start sending them
        bStatus = TRUE;
        iErr = m_sMySocket.SetSockOpt(SO_KEEPALIVE, &bStatus,
                                      sizeof(BOOL));
        if (iErr == SOCKET_ERROR)
        {
            // Do some error handling here
        }
    }
    // Continue processing
}

Determining If a Socket Is Blocking

When you are using the CSocket class, by default all socket communications functions block all thread processing until it has completed. If you have called the Connect function on a socket, the function will not return control of the thread until the connection has been completed, or the socket timeout has expired. The same thing is true for the Accept, Receive, and Send functions (along with the ReceiveFrom and SendTo functions). So what if you need to interrupt any of these functions before they return? There are two methods in the CSocket class that can be used for this purpose.

The first thing that you’ll need to do is to check to see if the socket is blocking a thread. You can use the IsBlocking method to determine if a socket is in a blocking function. This method doesn’t take any parameters, and returns a Boolean value that tells you if the socket is blocking or not.

After you have determined that a socket is blocking a thread, you can cancel the blocking method by calling the CancelBlockingCall method. This method will cause the socket to abort the function that is currently blocking, causing the blocking function to return with an error condition of WSAEINTR.


Caution:  

Using the CancelBlockingCall method to cancel any blocking function other than the Accept method can leave a socket in an unstable state. The only socket method that can be called with any predictability after a blocking function has been canceled is the Close method.


To determine if a socket is blocking, and if so, terminate it, you can do the following (in a second thread, of course):

if (m_sMySocket.IsBlocking())
    m_sMySocket.CancelBlockingCall();

Sockets and I/O Serialization

In those circumstances where the data that will be passed between the two applications communicating through a socket connection is of a known format, and can easily be serialized, there is a specialized MFC class specifically designed to enable you to serialize the communications. This class is the CSocketFile class. The CSocketFile class can be attached to an open CSocket class, and then treated just like a CFile class object.

When you have a connected CSocket, you can attach a CSocketFile class object to the CSocket object, specifying whether to make the CSocketFile archive compatible, as follows:

CSocketFile sMySocketFile(&m_sMySocket, TRUE);



The first parameter that you need to pass to the CSocketFile constructor is a pointer to the CSocket object that is to be serialized. The second parameter is a Boolean value specifying whether to make the CSocketFile object compatible with a CArchive object. By passing TRUE as the second parameter, you can now take the CSocketFile object and associate it with a CArchive object, as follows:

CArchive lArchive(&MySocketFile, CArchive::load);

From here, you can pass the CArchive object to the standard MFC Serialize function to read and write data to the socket connection.


Note:  

Serializing socket communications requires both connected applications to be reading and writing the same data format to the socket. If one of the two connected applications is not using the same serialized data format as the other, you’ll end up sending and receiving garbage data.


Building a Networked Application

To illustrate the basic Winsock functionality, you’ll create a simple dialog application that can function as either the client or server in a Winsock connection. This will enable you to run two copies of the sample application, one for each end of the connection, on the same computer or to copy the application to another computer so that you can run the two copies on separate computers and see how you can pass messages across a network. After the application has established a connection with another application, you will be able to enter text messages and send them to the other application. When the message has been sent, it will be added to a list of messages sent. Each message that is received will be copied into another list of all messages received. This will enable you to see the complete list of what is sent and received. It will also enable you to compare what one copy of the application has sent and what the other has received. (The two lists should be the same.)

Creating the Application Shell

For this application, just to keep things simple, you’ll create a dialog-style application. Everything that you are doing in this application can be done in an SDI or MDI application just as easily as with a dialog-style application. By using a dialog-style application, you are getting everything that might distract from the basic socket functionality (such as questions about whether the socket variable belongs in the document or view class, how much of the application functionality belongs in which of these two classes, and so on) away from the sample application.

To start the application, create a new MFC AppWizard project, giving the project a suitable name, such as Sock. On the first step of the AppWizard, specify that the application will be a dialog-based application. On the second step of the AppWizard, specify that the application should include support for Windows Sockets, as in Figure 24.3. You can accept the default settings for the rest of the options in the AppWizard. This will cause the AppWizard to include the AfxSocketInit function call in the application instance initialization.


Figure 24.3  Including Windows Sockets support.

Window Layout and Startup Functionality

After you create your application shell, you can lay out the main dialog for your application. On this dialog, you’ll need a set of radio buttons to specify whether the application is running as the client or server. You’ll also need several edit boxes for the computer name and port that the server will be listening on. Next, you’ll need a command button to start the application listening on the socket, or opening the connection to the server, and a button to close the connection. You’ll also need an edit box for entering the message to be sent to the other application and a button to send the message. Finally, you’ll need several list boxes into which you can add each of the messages sent and received. Place all these controls on the dialog, as shown in Figure 24.4, setting all of the control properties as specified in Table 24.5.


Figure 24.4  The main dialog layout.

Table 24.5 Control Property Settings

Object Property Setting

Group box ID IDC_STATICTYPE
Caption Socket Type
Radio button ID IDC_RCLIENT
Caption &Client
Group Checked
Radio button ID IDC_RSERVER
Caption &Server
Static text ID IDC_STATICNAME
Caption Server &Name:
Edit box ID IDC_ESERVNAME
Static text ID IDC_STATICPORT
Caption Server &Port:
Edit box ID IDC_ESERVPORT
Command button ID IDC_BCONNECT
Caption C&onnect
Command button ID IDC_BCLOSE
Caption C&lose
Disabled Checked
Static text ID IDC_STATICMSG
Caption &Message:
Disabled Checked
Edit box ID IDC_EMSG
Disabled Checked
Command button ID IDC_BSEND
Caption S&end
Disabled Checked
Static text ID IDC_STATIC
Caption Sent:
List box ID IDC_LSENT
Tab Stop Unchecked
Sort Unchecked
Selection None
Static text ID IDC_STATIC
Caption Received:
List box ID IDC_LRECVD
Tab Stop Unchecked
Sort Unchecked
Selection None



After you have the dialog designed, open the Class Wizard to attach variables to the controls on the dialog, as specified in Table 24.6.

Table 24.6 Control Variables

Object Name Category Type

IDC_BCONNECT m_ctlConnect Control CButton
IDC_EMSG m_strMessage Value CString
IDC_ESERVNAME m_strName Value CString
IDC_ESERVPORT m_iPort Value int
IDC_LRECVD m_ctlRecvd Control CListBox
IDC_LSENT m_ctlSent Control CListBox
IDC_RCLIENT m_iType Value int

So that you can reuse the Connect button to also place the server application into listen mode, you’ll add a function to the clicked event message for both of the two radio buttons, changing the text on the command button depending on which of the two is currently selected. To add this functionality to your application, add a function to the BN_CLICKED event message for the IDC_RCLIENT control ID, naming the function OnRType. Add the same function to the BN_CLICKED event message for the IDC_RSERVER control ID. Edit this function, adding the code in Listing 24.1.

Listing 24.1 The CSockDlg OnRType Function


void CSockDlg::OnRType()
{
    // TODO: Add your control notification handler code here
    // Sync the controls with the variables
    UpdateData(TRUE);
    // Which mode are we in?
    if (m_iType == 0)    // Set the appropriate text on the button
        m_ctlConnect.SetWindowText(“C&onnect”);
    else
        m_ctlConnect.SetWindowText(“&Listen”);
}

Now, if you compile and run your application, you should be able to select one and then the other of these two radio buttons, and the text on the command button should change to reflect the part the application will play, as in Figure 24.5.


Figure 24.5  Changing the button text.

Inheriting from the CAsyncSocket Class

So that you will be able to capture and respond to the socket events, you’ll create your own descendant class from CAsyncSocket. This class will need its own versions of the event functions, as well as a means of passing this event to the dialog that the object will be a member of. So that you can pass each of these events to the dialog-class level, you’ll add a pointer to the parent dialog class as a member variable of your socket class. You’ll use this pointer to call event functions for each of the socket events that are member functions of the dialog—after checking to make sure that no errors have occurred, of course.

To create this class in your application, select Insert, New Class from the menu. In the New Class dialog, leave the class type with the default value of MFC Class. Enter a name for your class, such as CMySocket, and select CAsyncSocket from the list of available base classes. This is all that you can specify on the New Class dialog, so click the OK button to add this new class to your application.

After you have created the socket class, add a member variable to the class to serve as a pointer to the parent dialog window. Specify the variable type as CDialog*, the variable name as m_pWnd, and the access as private. You also need to add a method to the class to set the pointer, so add a member function to your new socket class. Specify the function type as void, the declaration as SetParent(CDialog* pWnd), and the access as public. Edit this new function, setting the pointer passed as a parameter to the member variable pointer, as in Listing 24.2.

Listing 24.2 The CMySocket SetParent Function


void CMySocket::SetParent(CDialog *pWnd)
{
    // Set the member pointer
    m_pWnd = pWnd;
}

The only other thing that you need to do to your socket class is add the event functions, which you’ll use to call similarly named functions on the dialog class. To add a function for the OnAccept event function, add a member function to your socket class. Specify the function type as void, the function declaration as OnAccept(int nErrorCode), and the access as protected, and then check the virtual check box. Edit this function, adding the code in Listing 24.3.

Listing 24.3 The CMySocket OnAccept Function


void CMySocket::OnAccept(int nErrorCode)
{
    // Were there any errors?
    if (nErrorCode == 0)
        // No, call the dialog’s OnAccept function
        ((CSockDlg*)m_pWnd)->OnAccept();
}

Add similar functions to your socket class for the OnConnect, OnClose, OnReceive, and OnSend functions, calling same-named functions in the dialog class, which you’ll add later. After you’ve added all of these functions, you’ll need to include the header file for your application dialog in your socket class, as in line 7 of Listing 24.4.

Listing 24.4 The CMySocket include Statements


// MySocket.cpp: implementation file
//

#include “stdafx.h”
#include “Sock.h”
#include “MySocket.h”
#include “SockDlg.h”

After you’ve added all the necessary event functions to your socket class, you’ll add a variable of your socket class to the dialog class. For the server functionality, you’ll need two variables in the dialog class, one to listen for connection requests and the other to be connected to the other application. Because you will need two socket objects, add two member variables to the dialog class (CSockDlg). Specify the type of both variables as your socket class (CMySocket) and the access for both as private. Name one variable m_sListenSocket, to be used for listening for connection requests, and the other m_sConnectSocket, to be used for sending messages back and forth.

After you’ve added the socket variables, you’ll add the initialization code for all the variables. As a default, set the application type to client, the server name as loopback, and the port to 4000. Along with these variables, you’ll set the parent dialog pointers in your two socket objects so that they point to the dialog class. You can do this by adding the code in Listing 24.5 to the OnInitDialog function in the dialog class.




Note:  

The computer name loopback is a special name used in the TCP/IP network protocol to indicate the computer you are working on. It’s an internal computer name that is resolved to the network address 127.0.0.1. This is a computer name and address that is commonly used by applications that need to connect to other applications running on the same computer.


Listing 24.5 The CSockDlg OnInitDialog Function


BOOL CSockDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add “About...” menu item to system menu.

.
.
.
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // TODO: Add extra initialization here
    // Initialize the control variables
    m_iType = 0;
    m_strName = “loopback”;
    m_iPort = 4000;
    // Update the controls
    UpdateData(FALSE);
    // Set the socket dialog pointers
    m_sConnectSocket.SetParent(this);
    m_sListenSocket.SetParent(this);

    return TRUE;  // return TRUE  unless you set the focus to a control
}

Connecting the Application

When the user clicks the Connect button, you’ll disable all the top controls on the dialog. At this point, you don’t want the user to think that she is able to change the settings of the computer that she’s connecting to or change how the application is listening. You’ll call the Create function on the appropriate socket variable, depending on whether the application is running as the client or server. Finally, you’ll call either the Connect or Listen function to initiate the application’s side of the connection. To add this functionality to your application, open the Class Wizard and add a function to the BN_CLICKED event message for the Connect button (ID IDC_BCONNECT). Edit this function, adding the code in Listing 24.6.

Listing 24.6 The CSockDlg OnBconnect Function


void CSockDlg::OnBconnect()
{
    // TODO: Add your control notification handler code here
    // Sync the variables with the controls
    UpdateData(TRUE);
    // Disable the connection and type controls
    GetDlgItem(IDC_BCONNECT)->EnableWindow(FALSE);
    GetDlgItem(IDC_ESERVNAME)->EnableWindow(FALSE);
    GetDlgItem(IDC_ESERVPORT)->EnableWindow(FALSE);
    GetDlgItem(IDC_STATICNAME)->EnableWindow(FALSE);
    GetDlgItem(IDC_STATICPORT)->EnableWindow(FALSE);
    GetDlgItem(IDC_RCLIENT)->EnableWindow(FALSE);
    GetDlgItem(IDC_RSERVER)->EnableWindow(FALSE);
    GetDlgItem(IDC_STATICTYPE)->EnableWindow(FALSE);
    // Are we running as client or server?
    if (m_iType == 0)
    {
        // Client, create a default socket
        m_sConnectSocket.Create();
        // Open the connection to the server
        m_sConnectSocket.Connect(m_strName, m_iPort);
    }
    else
    {
        // Server, create a socket bound to the port specified
        m_sListenSocket.Create(m_iPort);
        // Listen for connection requests
        m_sListenSocket.Listen();
    }
}

Next, to complete the connection, you’ll add the socket event function to the dialog class for the OnAccept and OnConnect event functions. These are the functions that your socket class is calling. They don’t require any parameters, and they don’t need to return any result code. For the OnAccept function, which is called for the listening socket when another application is trying to connect to it, you’ll call the socket object’s Accept function, passing in the connection socket variable. After you’ve accepted the connection, you can enable the prompt and edit box for entering and sending messages to the other application.

To add this function to your application, add a member function to the dialog class (CSockDlg). Specify the function type as void, the declaration as OnAccept, and the access as public. Edit the function, adding the code in Listing 24.7.

Listing 24.7 The CSockDlg OnAccept Function


void CSockDlg::OnAccept()
{
    // Accept the connection request
    m_sListenSocket.Accept(m_sConnectSocket);
    // Enable the text and message controls
    GetDlgItem(IDC_EMSG)->EnableWindow(TRUE);
    GetDlgItem(IDC_BSEND)->EnableWindow(TRUE);
    GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE);
}

For the client side, there’s nothing to do after the connection has been completed except enable the controls for entering and sending messages. You’ll also enable the Close button so that the connection can be closed from the client side (but not the server side). To add this functionality to your application, add another member function to the dialog class (CSockDlg). Specify the function type as void, the function declaration as OnConnect, and the access as public. Edit the function, adding the code in Listing 24.8.

Listing 24.8 The CSockDlg OnConnect Function


void CSockDlg::OnConnect()
{
    // Enable the text and message controls
    GetDlgItem(IDC_EMSG)->EnableWindow(TRUE);
    GetDlgItem(IDC_BSEND)->EnableWindow(TRUE);
    GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE);
    GetDlgItem(IDC_BCLOSE)->EnableWindow(TRUE);
}

If you could compile and run your application now, you could start two copies, put one into listen mode, and then connect to it with the other. Unfortunately, you probably can’t even compile your application right now because your socket class is looking for several functions in your dialog class that you haven’t added yet. Add three member functions to the dialog class (CSockDlg). Specify all of them as void functions with public access. Specify the first function’s declaration as OnSend, the second as OnReceive, and the third as OnClose. You should now be able to compile your application.

After you’ve compiled your application, start two copies of the application, side by side. Specify that one of these two should be the server, and click the Listen button to put it into listen mode. Leave the other as the client and click the Connect button. You should see the connection controls disable and the message-sending controls enable as the connection is made, as in Figure 24.6.


Figure 24.6  Connecting the two applications.


Tip:  

Be sure that you have the server application listening before you try to connect to it with the client application. If you try to connect with the client before the server is listening for the connection, the connection will be rejected. Your application will not detect that the connection was rejected because you haven’t added any error handling to detect this event.



Tip:  

To run these applications and get them to connect, you’ll need TCP/IP running on your computer. If you have a network card in your computer, you might already have TCP/IP running. If you do not have a network card, and you use a modem to connect to the Internet, you will probably need to be connected to the Internet when you run and test these applications. When you connect to the Internet through a modem, your computer usually starts running TCP/IP after the connection to the Internet is made. If you do not have a network card in your computer, and you do not have any means of connecting to the Internet, or any other outside network that would allow you to run networked applications, you might not be able to run and test these applications on your computer.


Sending and Receiving

Now that you are able to connect the two running applications, you’ll need to add functionality to send and receive messages. After the connection is established between the two applications, the user will be able to enter text messages in the edit box in the middle of the dialog window and then click the Send button to send the message to the other application. After the message is sent, it will be added to the list box of sent messages. To provide this functionality, when the Send button is clicked, your application needs to check whether there is a message to be sent, get the length of the message, send the message, and then add the message to the list box. To add this functionality to your application, use the Class Wizard to add a function to the clicked event of the Send (IDC_BSEND) button. Edit this function, adding the code in Listing 24.9.



Listing 24.9 The CSockDlg OnBsend Function


void CSockDlg::OnBsend()
{
    // TODO: Add your control notification handler code here
    int iLen;
    int iSent;

    // Sync the controls with the variables
    UpdateData(TRUE);
    // Is there a message to be sent?
    if (m_strMessage != “”)
    {
        // Get the length of the message
        iLen = m_strMessage.GetLength();
        // Send the message
        iSent = m_sConnectSocket.Send(LPCTSTR(m_strMessage), iLen);
        // Were we able to send it?
        if (iSent == SOCKET_ERROR)
        {
        }
        else
        {
            // Add the message to the list box.
            m_ctlSent.AddString(m_strMessage);
            // Sync the variables with the controls
            UpdateData(FALSE);
        }
    }
}

When the OnReceive event function is triggered, indicating that a message has arrived, you’ll retrieve the message from the socket using the Receive function. After you’ve retrieved the message, you’ll convert it into a CString and add it to the message-received list box. You can add this functionality by editing the OnReceive function of the dialog class, adding the code in Listing 24.10.

Listing 24.10 The CSockDlg OnReceive Function


void CSockDlg::OnReceive()
{
    char *pBuf = new char[1025];
    int iBufSize = 1024;
    int iRcvd;
    CString strRecvd;

    // Receive the message
    iRcvd = m_sConnectSocket.Receive(pBuf, iBufSize);
    // Did we receive anything?
    if (iRcvd == SOCKET_ERROR)
    {
    }
    else
    {
        // Truncate the end of the message
        pBuf[iRcvd] = NULL;
        // Copy the message to a CString
        strRecvd = pBuf;
        // Add the message to the received list box
        m_ctlRecvd.AddString(strRecvd);
        // Sync the variables with the controls
        UpdateData(FALSE);
    }
}

At this point, you should be able to compile and run two copies of your application, connecting them as you did earlier. After you’ve got the connection established, you can enter a message in one application and send it to the other application, as shown in Figure 24.7.


Figure 24.7  Sending messages between the applications.

Ending the Connection

To close the connection between these two applications, the client application user can click the Close button to end the connection. The server application will then receive the OnClose socket event. The same thing needs to happen in both cases. The connected socket needs to be closed, and the message-sending controls need to be disabled. On the client, the connection controls can be enabled because the client could change some of this information and open a connection to another server application. Meanwhile, the server application continues to listen on the port that it was configured to listen to. To add all this functionality to your application, edit the OnClose function, adding the code in Listing 24.11.

Listing 24.11 The CSockDlg OnClose Function


void CSockDlg::OnClose()
{
    // Close the connected socket
    m_sConnectSocket.Close();
    // Disable the message sending controls
    GetDlgItem(IDC_EMSG)->EnableWindow(FALSE);
    GetDlgItem(IDC_BSEND)->EnableWindow(FALSE);
    GetDlgItem(IDC_STATICMSG)->EnableWindow(FALSE);
    GetDlgItem(IDC_BCLOSE)->EnableWindow(FALSE);
    // Are we running in Client mode?
    if (m_iType == 0)
    {
        // Yes, so enable the connection configuration controls
        GetDlgItem(IDC_BCONNECT)->EnableWindow(TRUE);
        GetDlgItem(IDC_ESERVNAME)->EnableWindow(TRUE);
        GetDlgItem(IDC_ESERVPORT)->EnableWindow(TRUE);
        GetDlgItem(IDC_STATICNAME)->EnableWindow(TRUE);
        GetDlgItem(IDC_STATICPORT)->EnableWindow(TRUE);
        GetDlgItem(IDC_RCLIENT)->EnableWindow(TRUE);
        GetDlgItem(IDC_RSERVER)->EnableWindow(TRUE);
        GetDlgItem(IDC_STATICTYPE)->EnableWindow(TRUE);
    }
}

Finally, for the Close button, call the OnClose function. To add this functionality to your application, use the Class Wizard to add a function to the clicked event for the Close button (IDC_BCLOSE). Edit the function to call the OnClose function, as in Listing 24.12.

Listing 24.12 The CSockDlg OnBclose Function


void CSockDlg::OnBclose()
{
    // TODO: Add your control notification handler code here
    // Call the OnClose function
    OnClose();
}

If you compile and run your application, you can connect the client application to the server, send some messages back and forth, and then disconnect the client by clicking the Close button. You’ll see the message-sending controls disable themselves in both applications, as in Figure 24.8. You can reconnect the client to the server by clicking the Connect button again and then pass some more messages between the two, as if they had never been connected in the first place. If you start a third copy of the application, change its port number, designate it as a server, and put it into listening mode, you can take your client back and forth between the two servers, connecting to one, closing the connection, changing the port number, and then connecting to the other.


Figure 24.8  Closing the connection between the applications.

Summary

This chapter discussed how you can enable your applications to communicate with others across a network, or across the Internet, by using the MFC Winsock classes. You took a good look at the CAsyncSocket and CSocket classes and learned how to create your own descendant class from them that would provide your applications with event-driven network communications.

You also learned how to create a server application that can listen for and accept connections from other applications. You discovered how to build a client application that can connect to a server, and how to send and receive messages over a socket connection between two applications.

Finally, you learned how you can use the CSocketFile class to serialize your network communications, enabling you to perform your network communications much as you would read and write to a file on your local hard drive.